
public class HelloWorld {

	public static void main(String[] args) {
		System.out.println("Hello world!");
		
		System.out.println(args[0]);
		System.out.println(args[1]);
		
		A1 a1 = new A1(3);
		System.out.println(a1);
		
		A2 a2 = new A2(1, 2);
		System.out.println(a2);
		
		Base b1 = new Base(new Zero());
		System.out.println(b1);
		
		Base b2 = new Base(new Salt());
		System.out.println(b2);
		
		System.out.println(new ManhattanPt(3, 4).distanceToO());
		System.out.println(new CartesianPt(3, 4).distanceToO());
		
		System.out.println(new Holder(Integer.valueOf(52)).whatHolder());
		System.out.println(new Holder(new Sword()).whatHolder());

		System.out.println(
				new Anchovy(
						new Cheese(
								new Crust()))
				.substituteAnchovyByCheese());
		
		System.out.println(new Onion(
				new Onion(
						new Skewer())).onlyOnions());

//		System.out.println(new Topping(
//				new AnchovyVer1(), 
//				new Bottom()).removeFish(
//						new AnchovyVer1()));
//		
//		System.out.println(new Integer(0).equals(new Integer(0)));
//		System.out.println(new Topping(
//				new Integer(2), new Topping(
//						new Integer(3), new Topping(2,
//								new Bottom()))).removeInt(new Integer(3)));
		
		System.out.println(new Topping(
				new AnchovyVer1(), 
				new Bottom()).remove(
						new AnchovyVer1()));

		System.out.println(new Topping(
				Integer.valueOf(2), new Topping(
						Integer.valueOf(3), new Topping(2,
								new Bottom()))).remove(Integer.valueOf(3)));
		
		System.out.println(new Topping(
				new AnchovyVer1(),
				new Topping(Integer.valueOf(3),
						new Topping(new Zero(), 
								new Bottom()))).remove(new Zero()));
		
		System.out.println(new Topping(
				new AnchovyVer1(),
				new Topping(Integer.valueOf(3),
						new Topping(new OneMoreThan(new Zero()), 
								new Bottom()))).remove(
										new OneMoreThan(new Zero())));
	}
}

//----------------------------------------------------
abstract class Seasoning {
	
}

class Salt extends Seasoning {
	public String toString() {
		return "new " + getClass().getName();
	}
}

class Pepper extends Seasoning {
	public String toString() {
		return "new " + getClass().getName();
	}
}

class Thyme extends Seasoning {
	public String toString() {
		return "new " + getClass().getName();
	}
}

class Sage extends Seasoning {
	public String toString() {
		return "new " + getClass().getName();
	}
}

//----------------------------------------------------

abstract class Point {
	int x;
	int y;
	
	Point(int _x, int _y) {
		x = _x;
		y = _y;
	}
	
	abstract int distanceToO();
	
	boolean closerToO(Point p) {
		return distanceToO() <= p.distanceToO();
	}
}

class CartesianPt extends Point {
	CartesianPt(int _x, int _y) {
		super(_x, _y);
	}

	@Override
	int distanceToO() {
		return (int)Math.sqrt(x * x + y * y);
	}
}

class ManhattanPt extends Point {
	ManhattanPt(int _x, int _y) {
		super(_x, _y);
	}

	@Override
	int distanceToO() {
		return Math.abs(x) + Math.abs(y);
	}
}

//----------------------------------------------------

abstract class NumD {
	
}

class Zero extends NumD {
	public String toString() {
		return "new " + getClass().getName();
	}

	@Override
	public boolean equals(Object o) {
		return (o instanceof Zero);
	}
}

class OneMoreThan extends NumD {
	@Override
	public String toString() {
		return "new OneMoreThan(predecessor=" + predecessor + ")";
	}

	NumD predecessor;
	
	OneMoreThan(NumD _p) {
		predecessor = _p;
	}

	@Override
	public boolean equals(Object o) {
		if (o instanceof OneMoreThan) {
			return predecessor.equals(
					((OneMoreThan)o).predecessor);
		} else {
			return false;
		}
	}
}

//----------------------------------------------------

abstract class Layer {
	
}

class Base extends Layer {
	Object o;
	
	Base(Object _o) {
		o = _o;
	}
	
	public String toString() {
		return "new " + getClass().getName() + "(" + o + ")";
	}
}

class Slice extends Layer {
	Layer l;
	
	Slice(Layer _l) {
		l = _l;
	}
}

//----------------------------------------------------

abstract class Shish {
	OnlyOnionsV ooFn = new OnlyOnionsV();
	abstract boolean onlyOnions();

	IsVegetarian ivFn = new IsVegetarian();
	abstract boolean isVegetarian();
}

class Skewer extends Shish {

	@Override
	boolean onlyOnions() {
		return ooFn.forSkewer();
	}

	@Override
	boolean isVegetarian() {
		return ivFn.forSkewer();
	}
	
}

class Onion extends Shish {
	Shish s;
	Onion(Shish _s) {
		s = _s;
	}
	@Override
	boolean onlyOnions() {
		return ooFn.forOnion(s);
	}
	@Override
	boolean isVegetarian() {
		return ivFn.forOnion(s);
	}
}

class Lamb extends Shish {
	Shish s;
	Lamb(Shish _s) {
		s = _s;
	}
	@Override
	boolean onlyOnions() {
		return ooFn.forLamb(s);
	}
	
	@Override
	boolean isVegetarian() {
		return ivFn.forLamb(s);
	}
}

class Tomato extends Shish {
	Shish s;
	Tomato(Shish _s) {
		s = _s;
	}
	@Override
	boolean onlyOnions() {
		return ooFn.forTomato(s);
	}
	@Override
	boolean isVegetarian() {
		return ivFn.forTomato(s);
	}
}

//----------------------------------------------------
// a visitor class
class OnlyOnionsV {
	boolean forSkewer() {
		return true;
	}
	boolean forOnion(Shish s) {
		return s.onlyOnions();
	}
	boolean forLamb(Shish s) {
		return false;
	}
	boolean forTomato(Shish s) {
		return false;
	}
}

//----------------------------------------------------

class IsVegetarian {
	boolean forSkewer() {
		return true;
	}
	boolean forOnion(Shish s) {
		return s.isVegetarian();
	}
	boolean forLamb(Shish s) {
		return false;
	}
	boolean forTomato(Shish s) {
		return s.isVegetarian();
	}
}

// ----------------------------------------------------
abstract class Kebab {
	abstract boolean isVeggie();
	abstract Object whatHolder();
}

class Holder extends Kebab {

	@Override
	public String toString() {
		return "new Holder (o=" + o + ")";
	}
	Object o;
	Holder(Object _o) {
		o = _o;
	}
	@Override
	boolean isVeggie() {
		return true;
	}
	@Override
	Object whatHolder() {
		return o;
	}
}

class Shallot extends Kebab {
	Kebab k;
	Shallot(Kebab _k) {
		k = _k;
	}
	@Override
	boolean isVeggie() {
		return k.isVeggie();
	}
	@Override
	Object whatHolder() {
		return k.whatHolder();
	}
}

class Shrimp extends Kebab {
	Kebab k;
	Shrimp(Kebab _k) {
		k = _k;
	}
	@Override
	boolean isVeggie() {
		return false;
	}
	@Override
	Object whatHolder() {
		return k.whatHolder();
	}
}

class Radish extends Kebab {
	Kebab k;
	Radish(Kebab _k) {
		k = _k;
	}
	@Override
	boolean isVeggie() {
		return k.isVeggie();
	}
	@Override
	Object whatHolder() {
		return k.whatHolder();
	}
}

class PepperV1 extends Kebab {
	Kebab k;
	
	PepperV1(Kebab _k) {
		k = _k;
	}

	@Override
	boolean isVeggie() {
		return k.isVeggie();
	}

	@Override
	public String toString() {
		return "new PepperV1 (k=" + k + ")";
	}

	@Override
	Object whatHolder() {
		return k.whatHolder();
	}
}

class Zucchini extends Kebab {

	Kebab k;
	
	@Override
	public String toString() {
		return "new Zucchini (k=" + k + ")";
	}

	Zucchini(Kebab _k) {
		k = _k;
	}
	
	@Override
	boolean isVeggie() {
		return k.isVeggie();
	}

	@Override
	Object whatHolder() {
		return k.whatHolder();
	}
	
}

//----------------------------------------------------
abstract class Rod {
	
}

class Dagger extends Rod {

	@Override
	public String toString() {
		return "new Dagger ()";
	}
	
}

class Sabre extends Rod {

	@Override
	public String toString() {
		return "new Sabre ()";
	}
	
}

class Sword extends Rod {

	@Override
	public String toString() {
		return "new Sword ()";
	}
	
}
//----------------------------------------------------
abstract class Plate {
	
}

class Gold extends Plate {

	@Override
	public String toString() {
		return "new Gold ()";
	}
	
}

class Silver extends Plate {

	@Override
	public String toString() {
		return "new Silver ()";
	}
	
}

class Brass extends Plate {

	@Override
	public String toString() {
		return "new Brass ()";
	}
	
}

class Copper extends Plate {

	@Override
	public String toString() {
		return "new Copper ()";
	}
	
}

class Wood extends Plate {

	@Override
	public String toString() {
		return "new Wood ()";
	}
	
}

//----------------------------------------------------
// visitor class
class RemoveAnchovyV {
	Pizza forCrust() {
		return new Crust();
	}
	
	Pizza forCheese(Pizza p) {
		return new Cheese(p.removeAnchovy());
	}
	
	Pizza forOlive(Pizza p) {
		return new Olive(p.removeAnchovy());
	}
	
	Pizza forAnchovy(Pizza p) {
		return p.removeAnchovy();
	}
	
	Pizza forSausage(Pizza p) {
		return new Sausage(p.removeAnchovy());
	}
	
	Pizza forSpinach(Pizza p) {
		return new Spinach(p.removeAnchovy());
	}
}

//----------------------------------------------------
// visitor class
class TopAnchovyWithCheeseV {
	Pizza forCrust() {
		return new Crust();
	}
	
	Pizza forCheese(Pizza p) {
		return new Cheese(p.topAnchovyWithCheese());
	}
	
	Pizza forOlive(Pizza p) {
		return new Olive(p.topAnchovyWithCheese());
	}
	
	Pizza forAnchovy(Pizza p) {
		return new Cheese(new Anchovy(p.topAnchovyWithCheese()));
	}
	
	Pizza forSausage(Pizza p) {
		return new Sausage(p.topAnchovyWithCheese());
	}
	
	Pizza forSpinach(Pizza p) {
		return new Spinach(p.topAnchovyWithCheese());
	}
}

//----------------------------------------------------

abstract class Pizza {
	RemoveAnchovyV removeAnchovyFn = new RemoveAnchovyV();
	abstract Pizza removeAnchovy();
	
	abstract Pizza topAnchovyWithCheese();
	TopAnchovyWithCheeseV topAnchovyWithCheeseFn = new TopAnchovyWithCheeseV();
	
	// an optimize
	Pizza substituteAnchovyByCheese() {
		return this.topAnchovyWithCheese().removeAnchovy();
	}
}

class Crust extends Pizza {

	@Override
	public String toString() {
		return "new Crust ()";
	}

	@Override
	Pizza removeAnchovy() {
		return removeAnchovyFn.forCrust();
	}

	@Override
	Pizza topAnchovyWithCheese() {
		return topAnchovyWithCheeseFn.forCrust();
	}

	@Override
	Pizza substituteAnchovyByCheese() {
		return new Crust();
	}
}

class Cheese extends Pizza {
	Pizza p;
	
	Cheese(Pizza _p) {
		p = _p;
	}

	@Override
	public String toString() {
		return "new Cheese (p=" + p + ")";
	}

	@Override
	Pizza removeAnchovy() {
		return removeAnchovyFn.forCheese(p);
	}

	@Override
	Pizza topAnchovyWithCheese() {
		return topAnchovyWithCheeseFn.forCheese(p);
	}
}

class Olive extends Pizza {
	Pizza p;
	
	Olive(Pizza _p) {
		p = _p;
	}

	@Override
	public String toString() {
		return "new Olive (p=" + p + ")";
	}

	@Override
	Pizza removeAnchovy() {
		return removeAnchovyFn.forOlive(p);
	}

	@Override
	Pizza topAnchovyWithCheese() {
		return topAnchovyWithCheeseFn.forOlive(p);
	}
}

class Anchovy extends Pizza {
	Pizza p;
	
	Anchovy(Pizza _p) {
		p = _p;
	}

	@Override
	public String toString() {
		return "new Anchovy (p=" + p + ")";
	}

	@Override
	Pizza removeAnchovy() {
		return removeAnchovyFn.forAnchovy(p);
	}

	@Override
	Pizza topAnchovyWithCheese() {
		return topAnchovyWithCheeseFn.forAnchovy(p);
	}
}

class Sausage extends Pizza {
	Pizza p;
	
	Sausage(Pizza _p) {
		p = _p;
	}

	@Override
	public String toString() {
		return "new Sausage (p=" + p + ")";
	}

	@Override
	Pizza removeAnchovy() {
		return removeAnchovyFn.forSausage(p);
	}

	@Override
	Pizza topAnchovyWithCheese() {
		return topAnchovyWithCheeseFn.forSausage(p);
	}
}

class Spinach extends Pizza {
	Pizza p;
	
	Spinach(Pizza _p) {
		p = _p;
	}

	@Override
	public String toString() {
		return "new Spinach(p=" + p + ")";
	}

	@Override
	Pizza removeAnchovy() {
		return removeAnchovyFn.forSpinach(p);
	}

	@Override
	Pizza topAnchovyWithCheese() {
		return topAnchovyWithCheeseFn.forSpinach(p);
	}
	
}

//----------------------------------------------------
// visitor class
// (version 1, visitor)
//class RemoveAnchovyVer1V {
//	PizzaPie forBottom() {
//		return new Bottom();
//	}
//
//	PizzaPie forTopping(Object t, PizzaPie r) {
//		if (new AnchovyVer1().equals(t)) {
//			return r.removeAnchovyVer1();
//		} else {
//			return new Topping(t, r.removeAnchovyVer1());
//		}
//	}
//}

//----------------------------------------------------
// visitor class
// Page 74, Chapter 5
//class RemoveFishV {
//	PizzaPie forBottom(Fish f) {
//		return new Bottom();
//	}
//
//	PizzaPie forTopping(Fish f, Object t, PizzaPie r) {
//		if (f.equals(t)) {
//			return r.removeFish(f);
//		} else {
//			return new Topping(t, r.removeFish(f));
//		}
//	}
//}

//----------------------------------------------------
// visitor class
//class RemoveIntV {
//	PizzaPie forBottom(Integer i) {
//		return new Bottom();
//	}
//	
//	PizzaPie forTopping(Integer i, Object t, PizzaPie r) {
//		if (i.equals(t)) {
//			return r.removeInt(i);
//		} else {
//			return new Topping(t, r.removeInt(i));
//		}
//	}
//}

//----------------------------------------------------

class RemoveV {
	PizzaPie forBottom(Object i) {
		return new Bottom();
	}
	
	PizzaPie forTopping(Object o, Object t, PizzaPie r) {
		if (o.equals(t)) {
			return r.remove(o);
		} else {
			return new Topping(t, r.remove(o));
		}
	}
}

//----------------------------------------------------

class SubstituteFishV {
	PizzaPie forBottom(Fish fNew, Fish fOld) {
		return new Bottom();
	}

	PizzaPie forTopping(Object t, PizzaPie r,
			Fish fNew, Fish fOld) {
		if (fOld.equals(t)) {
			return new Topping(fNew, r.substituteFish(fNew, fOld));
		} else {
			return new Topping(t, r.substituteFish(fNew, fOld));
		}
	}
}

//----------------------------------------------------

class SubstituteV {
	PizzaPie forBottom(Object oNew, Object oOld) {
		return new Bottom();
	}

	PizzaPie forTopping(Object t, PizzaPie r,
			Object oNew, Object oOld) {
		if (oOld.equals(t)) {
			return new Topping(oNew, r.substitute(oNew, oOld));
		} else {
			return new Topping(t, r.substitute(oNew, oOld));
		}
	}
}

//----------------------------------------------------

abstract class PizzaPie {
//	RemoveAnchovyVer1V removeAnchovyVer1Fn = new RemoveAnchovyVer1V();
//	abstract PizzaPie removeAnchovyVer1();
//
//	RemoveFishV removeFishFn = new RemoveFishV();
//	abstract PizzaPie removeFish(Fish f);
//
//	RemoveIntV removeIntFn = new RemoveIntV();
//	abstract PizzaPie removeInt(Integer i);

	RemoveV removeFn = new RemoveV();
	abstract PizzaPie remove(Object o);
	
	SubstituteFishV substituteFishFn = new SubstituteFishV();
	abstract PizzaPie substituteFish(Fish fNew, Fish fOld);

	SubstituteV substituteFn = new SubstituteV();
	abstract PizzaPie substitute(Object oNew, Object oOld);
}

class Bottom extends PizzaPie {

	@Override
	public String toString() {
		return "new Bottom()";
	}

//	@Override
//	PizzaPie removeAnchovyVer1() {
//		return removeAnchovyVer1Fn.forBottom();
//	}
//
//	@Override
//	PizzaPie removeFish(Fish f) {
//		return removeFishFn.forBottom(f);
//	}
//
//	@Override
//	PizzaPie removeInt(Integer i) {
//		return removeIntFn.forBottom(i);
//	}

	@Override
	PizzaPie remove(Object o) {
		return removeFn.forBottom(o);
	}

	@Override
	PizzaPie substituteFish(Fish fNew, Fish fOld) {
		return substituteFishFn.forBottom(fNew, fOld);
	}

	@Override
	PizzaPie substitute(Object oNew, Object oOld) {
		return substituteFn.forBottom(oNew, oOld);
	}
}

class Topping extends PizzaPie {
	@Override
	public String toString() {
		return "new Topping(t=" + t + ", r=" + r + ")";
	}

	Object t;
	PizzaPie r;
	
	Topping(Object _t, PizzaPie _r) {
		t = _t;
		r = _r;
	}

//	@Override
//	PizzaPie removeAnchovyVer1() {
//		return removeAnchovyVer1Fn.forTopping(t, r);
//	}
//
//	@Override
//	PizzaPie removeFish(Fish f) {
//		return removeFishFn.forTopping(f, t, r);
//	}
//
//	@Override
//	PizzaPie removeInt(Integer i) {
//		return removeIntFn.forTopping(i, t, r);
//	}

	@Override
	PizzaPie remove(Object o) {
		return removeFn.forTopping(o, t, r);
	}

	@Override
	PizzaPie substituteFish(Fish fNew, Fish fOld) {
		return substituteFishFn.forTopping(t, r, fNew, fOld);
	}

	@Override
	PizzaPie substitute(Object oNew, Object oOld) {
		return substituteFn.forTopping(t, r, oNew, oOld);
	}
}

//----------------------------------------------------

abstract class Fish {
	
}

class AnchovyVer1 extends Fish {
	@Override
	public String toString() {
		return "new AnchovyVer1()";
	}

	public boolean equals(Object o) {
		return (o instanceof AnchovyVer1);
	}
}

class Salmon extends Fish {
	public boolean equals(Object o) {
		return (o instanceof Salmon);
	}

	@Override
	public String toString() {
		return "new Salmon()";
	}
}

class Tuna extends Fish {
	public boolean equals(Object o) {
		return (o instanceof Tuna);
	}

	@Override
	public String toString() {
		return "new Tuna()";
	}
}

//----------------------------------------------------

class A1 {
	int x;
	A1(int x) {
		this.x = x;
	}
	public String toString() {
		return "new " + getClass().getName() + "(" + x +
				")";
	}
}

class A2 {
	int x;
	int y;
	
	A2(int x, int y) {
		this.x = x;
		this.y = y;
	}
	public String toString() {
		return "new " + getClass().getName() + "(" + x + ", "
				+ y + ")";
	}
}
